package org.zkoss.zktc.console;

import groovy.lang.Binding;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.junit.Assert;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zkplus.databind.DataBinder;
import org.zkoss.zktc.core.DriverLoader;
import org.zkoss.zktc.core.Variables;
import org.zkoss.zktc.core.groovy.BindingLoader;
import org.zkoss.zktc.core.groovy.ExecutionException;
import org.zkoss.zktc.core.groovy.Snippet;
import org.zkoss.zktc.core.groovy.SnippetRunner;
import org.zkoss.zktc.core.widget.WidgetDriver;
import org.zkoss.zul.Button;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Textbox;

public class MainComposer extends GenericForwardComposer {
	private static final long serialVersionUID = 1L;
	WidgetDriver driver;
	Properties config;
	SnippetRunner runner;
	Binding binding;
	
	Button startBtn;
	Button runstepsBtn;
	Button runallBtn;
	Button endBtn;
	Button clearBtn;
	Button stepoverBtn;
	
	Textbox snippetEditor;
	Textbox snippetStatus;
	Textbox variableEditor;
	Listbox snippetListbox;
	Listbox variableListbox;
	
	SnippetListModel snippetModel = null;
	ListModelList variableModel = null;
	List<Snippet> snippets = null;
	

	Map<String,Object> selectedSnippet;
	Map<String,Object> selectedVariable;
	
	/*binding will auto inject to this composer after doAfterComposer*/
	DataBinder binder;
	
	static Assert asserter = new Assert(){};

	public void doAfterCompose(Component comp) throws Exception{
		super.doAfterCompose(comp);
		
		config = DriverLoader.loadConfig("zktc-console.properties");
		
		binder = (DataBinder)comp.getPage().getAttribute("binder");
		
		snippetModel = new SnippetListModel();
		variableModel = new ListModelList();
	}
	
	
	private void reloadSnippetModel(){
		snippets = new SnippetLoader().loadSnippets();
		snippetModel.clear();
		for(Snippet s:snippets){
			final Snippet it = s;
			Map m = new HashMap<String,Object>(){
				public Object get(Object name){
					if("index".equals(name)){
						return snippets.indexOf(it)+1;
					}else if("stop".equals(name)){
						return runner!=null && snippets.indexOf(it)==runner.getIndex();
					}else if("sclass".equals(name)){
						return "snippet snippet-"+it.getStatus();
					}else if("error".equals(name)){
						return it.getStatus() == Snippet.Status.ERROR;
					}else if("fine".equals(name)){
						return it.getStatus() == Snippet.Status.FINE;
					}
					return super.get(name);
				}
			};
			m.put("it", it);
			snippetModel.add(m);
		}
		binder.loadComponent(snippetListbox);
	}
	
	private void reloadVariableModel(){
		variableModel.clear();
		if(binding!=null){
			for(Object s:binding.getVariables().keySet()){
				Map m = new HashMap<String,Object>();
				m.put("name", s.toString());
				m.put("value", binding.getVariable(s.toString()));
				variableModel.add(m);
			}
		}
		selectedVariable = null;
		binder.loadComponent(variableListbox);
	}
	

	public Map<String,Object> getSelectedSnippet() {
		return selectedSnippet;
	}

	public void setSelectedSnippet(Map<String,Object> selectedSnippet) {
		this.selectedSnippet = selectedSnippet;
	}

	public Map<String, Object> getSelectedVariable() {
		return selectedVariable;
	}

	public void setSelectedVariable(Map<String, Object> selectedVariable) {
		this.selectedVariable = selectedVariable;
	}
	
	public ListModel getSnippetModel(){
		return snippetModel;
	}
	
	public ListModel getVariableModel(){
		return variableModel;
	}

	private WidgetDriver newDriver() {
		return DriverLoader.loadWidgetDrvier(config);
	}
	
	private void quitDriver(){
		try{
			if(driver!=null){
				driver.quit();
			}
		}catch(Exception x){
			try {
				Messagebox.show(x.getMessage());
			} catch (InterruptedException e) {
			}
		}
		driver = null;
	}
	
	private Binding newBinding(){
		binding = new Binding() {
			// allow everything
			public Object getVariable(String name) {
				if (Variables.DRIVER.equals(name)) {
					return driver;
				}else if (Variables.ASSERTER.equals(name)) {
					return asserter;
				}
				return super.getVariables().get(name);
			}
		};
		BindingLoader.load(config, binding);
		return binding;
	}
	
	private SnippetRunner newRunner(){
		Binding binding = newBinding();
		SnippetRunner runner = new SnippetRunner(snippets,binding);
		for(Snippet s:snippets){
			s.clearStatus();
		}
		return runner;
	}
	
	public void onCreate(){
		reloadSnippetModel();
		reloadVariableModel();
	}
	
	//Events
	public void onClick$startBtn(){
		quitDriver();
		driver = newDriver();
		runner = newRunner();
		reloadVariableModel();
		binder.loadComponent(snippetListbox);
		reloadBtn();
	}
	
	public void onClick$runallBtn(){
		run();
		reloadVariableModel();
	}
	public void onClick$runstepsBtn(){
		runSteps();
	}
	public void onClick$stepoverBtn(){
		stepOver();
		reloadVariableModel();
	}
	
	public void onSelect$snippetListbox(){
		loadSnippetStatus();
	}
	public void onClick$snippetListbox(){
		loadSnippetStatus();	
	}
	
	public void onSelect$variableListbox(){
		loadSelectedVariable();
	}
	public void onClick$variableListbox(){
		loadSelectedVariable();	
	}
	
	private void loadSnippetStatus(){
		Snippet snippet = (Snippet)selectedSnippet.get("it");
		snippetEditor.setText(snippet.getCode());
		StringBuilder message = new StringBuilder();
		
		switch(snippet.getStatus()){
		case ERROR:
			if(snippet.hasErr()){
				message.append(snippet.getErrMessage()).append("\n");
				Exception e = snippet.getErr();
				if(e!=null){
					StringWriter w = new StringWriter();
					e.printStackTrace(new PrintWriter(w));
					message.append(w.toString());
				}
			}else{
				message.append("Error");
			}
			break;
		case FINE:
			message.append("Fine");
			break;
		case NONE:
			break;
		}
		
		snippetStatus.setText(message.toString());
	}
	
	private void loadSelectedVariable(){
		if(selectedVariable!=null){
			Object value = selectedVariable.get("value");
			value = value==null?"null":value.toString();
			variableEditor.setText((String)value);
		}else{
			variableEditor.setText("");
		}
		
	}
	
	public void onChange$snippetEditor(){
		if(selectedSnippet!=null){
			((Snippet)selectedSnippet.get("it")).setCode(snippetEditor.getText());
			binder.loadComponent(snippetListbox.getSelectedItem());
		}
	}
	public void onClick$clearBtn(){
		runner = newRunner();
		reloadVariableModel();
		binder.loadComponent(snippetListbox);
		driver.navigate("");
		reloadBtn();
	}
	public void onClick$endBtn(){
		for(Snippet s:snippets){
			s.clearStatus();
		}
		quitDriver();
		runner = null;
		binder.loadComponent(snippetListbox);
		reloadBtn();
	}
	
	public void onRunSteps(){
		runSteps();
	}
	
	public void run(){
		if(runner!=null && runner.hasNextStep()){
			int index1 = runner.getIndex();
			try {
				runner.run();
			} catch (ExecutionException e) {
				e.printStackTrace();
				try {
					Messagebox.show(e.getMessage());
				} catch (InterruptedException e1) {
				}
			}
			int index2 = runner.hasNextStep()?runner.getIndex():runner.getIndex()-1;

			for(;index1<=index2;index1++){
				binder.loadComponent(snippetListbox.getItemAtIndex(index1));
			}
			selectedSnippet = (Map<String, Object>) (runner.hasNextStep()?snippetModel.get(index2):null);
			if(selectedSnippet!=null){
				snippetListbox.setSelectedIndex(index2);
			}else{
				snippetListbox.clearSelection();
			}
		}
	}
	public void runSteps(){
		if(stepOver() &&runner.hasNextStep()){
			Events.echoEvent("onRunSteps", self, "");
		}else{
			reloadVariableModel();
		}
	}
	
	private boolean stepOver(){
		boolean r = false;
		if(runner!=null && runner.hasNextStep()){
			r = true;
			int index = runner.getIndex();
			try {
				runner.stepOver();
				
			} catch (ExecutionException e) {
				e.printStackTrace();
				r = false;
				try {
					Messagebox.show(e.getMessage());
				} catch (InterruptedException e1) {
					
				}
			}
			binder.loadComponent(snippetListbox.getItemAtIndex(index));
			int index2 = runner.hasNextStep()?runner.getIndex():runner.getIndex()-1;
			if(index!=index2){
				binder.loadComponent(snippetListbox.getItemAtIndex(index2));
			}
//			snippetModel.fireChanged(index-1,index);
			selectedSnippet = (Map<String, Object>) (runner.hasNextStep()?snippetModel.get(index2):null);
			if(selectedSnippet!=null){
				snippetListbox.setSelectedIndex(index2);
			}else{
				snippetListbox.clearSelection();
			}
		}
		return r;
	}
	
	private void reloadBtn(){
		runallBtn.setDisabled(driver==null);
		runstepsBtn.setDisabled(driver==null);
		stepoverBtn.setDisabled(driver==null);
		clearBtn.setDisabled(driver==null);
		endBtn.setDisabled(driver==null);
		
		
	}
	
}
